package loader

import (
	"path/filepath"
	"strings"
	"sync"
	"time"
)

// PriorityManager manages loading priorities based on various factors
type PriorityManager struct {
	mu                sync.RWMutex
	priorityRules     []PriorityRule
	filePriorities    map[string]float64
	accessHistory     map[string]*AccessInfo
	patternAnalyzer   *PatternAnalyzer
}

// PriorityRule defines a rule for determining loading priority
type PriorityRule struct {
	Name        string
	Priority    float64
	Condition   func(path string, info *AccessInfo) bool
	Description string
}

// AccessInfo contains information about file access patterns
type AccessInfo struct {
	Path           string
	AccessCount    int64
	LastAccess     time.Time
	FirstAccess    time.Time
	AverageInterval time.Duration
	AccessPattern  string
	Priority       float64
}

// PatternAnalyzer analyzes access patterns for priority determination
type PatternAnalyzer struct {
	mu              sync.RWMutex
	patterns        map[string]*AccessPattern
	learningEnabled bool
}

// AccessPattern represents a learned access pattern
type AccessPattern struct {
	Pattern      string
	Frequency    int64
	Confidence   float64
	LastObserved time.Time
}

// NewPriorityManager creates a new priority manager
func NewPriorityManager() *PriorityManager {
	pm := &PriorityManager{
		priorityRules:   getDefaultPriorityRules(),
		filePriorities:  make(map[string]float64),
		accessHistory:   make(map[string]*AccessInfo),
		patternAnalyzer: NewPatternAnalyzer(),
	}

	return pm
}

// CalculatePriority calculates the loading priority for a file
func (pm *PriorityManager) CalculatePriority(path string, event FileEvent) float64 {
	pm.mu.Lock()
	defer pm.mu.Unlock()

	// Update access history
	pm.updateAccessHistory(path, event)

	// Get access info
	info := pm.accessHistory[path]
	if info == nil {
		info = &AccessInfo{
			Path:        path,
			FirstAccess: time.Now(),
			LastAccess:  time.Now(),
			AccessCount: 1,
		}
		pm.accessHistory[path] = info
	}

	// Calculate base priority from rules
	basePriority := pm.calculateBasePriority(path, info)

	// Apply pattern-based adjustments
	patternAdjustment := pm.patternAnalyzer.Analyze(path, event)

	// Apply temporal adjustments
	temporalAdjustment := pm.calculateTemporalAdjustment(path, info)

	// Calculate final priority
	finalPriority := basePriority * (1.0 + patternAdjustment + temporalAdjustment)

	// Store calculated priority
	pm.filePriorities[path] = finalPriority

	return finalPriority
}

// calculateBasePriority calculates base priority from rules
func (pm *PriorityManager) calculateBasePriority(path string, info *AccessInfo) float64 {
	maxPriority := 0.0

	for _, rule := range pm.priorityRules {
		if rule.Condition(path, info) {
			if rule.Priority > maxPriority {
				maxPriority = rule.Priority
			}
		}
	}

	// Default priority if no rules match
	if maxPriority == 0.0 {
		maxPriority = 0.5
	}

	return maxPriority
}

// calculateTemporalAdjustment calculates temporal-based priority adjustments
func (pm *PriorityManager) calculateTemporalAdjustment(_ string, info *AccessInfo) float64 {
	now := time.Now()
	adjustment := 0.0

	// Recent access bonus
	if now.Sub(info.LastAccess) < 5*time.Minute {
		adjustment += 0.2
	}

	// Frequent access bonus
	if info.AccessCount > 10 {
		adjustment += 0.3
	}

	// Access interval pattern
	if info.AverageInterval > 0 && info.AverageInterval < time.Hour {
		adjustment += 0.1
	}

	return adjustment
}

// updateAccessHistory updates the access history for a file
func (pm *PriorityManager) updateAccessHistory(path string, event FileEvent) {
	info, exists := pm.accessHistory[path]
	if !exists {
		info = &AccessInfo{
			Path:        path,
			FirstAccess: time.Now(),
			AccessCount: 0,
		}
		pm.accessHistory[path] = info
	}

	// Update access count and timing
	oldLastAccess := info.LastAccess
	info.LastAccess = time.Now()
	info.AccessCount++

	// Calculate average access interval
	if !oldLastAccess.IsZero() {
		interval := info.LastAccess.Sub(oldLastAccess)
		if info.AverageInterval == 0 {
			info.AverageInterval = interval
		} else {
			info.AverageInterval = (info.AverageInterval + interval) / 2
		}
	}

	// Update access pattern
	pm.updateAccessPattern(info)
}

// updateAccessPattern updates the access pattern for a file
func (pm *PriorityManager) updateAccessPattern(info *AccessInfo) {
	interval := info.AverageInterval

	if interval < time.Minute {
		info.AccessPattern = "frequent"
	} else if interval < time.Hour {
		info.AccessPattern = "regular"
	} else if interval < 24*time.Hour {
		info.AccessPattern = "daily"
	} else {
		info.AccessPattern = "sporadic"
	}
}

// GetPriority returns the calculated priority for a file
func (pm *PriorityManager) GetPriority(path string) float64 {
	pm.mu.RLock()
	defer pm.mu.RUnlock()

	if priority, exists := pm.filePriorities[path]; exists {
		return priority
	}

	return 0.5 // Default priority
}

// GetAccessInfo returns access information for a file
func (pm *PriorityManager) GetAccessInfo(path string) (*AccessInfo, bool) {
	pm.mu.RLock()
	defer pm.mu.RUnlock()

	info, exists := pm.accessHistory[path]
	return info, exists
}

// AddPriorityRule adds a new priority rule
func (pm *PriorityManager) AddPriorityRule(rule PriorityRule) {
	pm.mu.Lock()
	defer pm.mu.Unlock()

	pm.priorityRules = append(pm.priorityRules, rule)
}

// RemovePriorityRule removes a priority rule by name
func (pm *PriorityManager) RemovePriorityRule(name string) bool {
	pm.mu.Lock()
	defer pm.mu.Unlock()

	for i, rule := range pm.priorityRules {
		if rule.Name == name {
			pm.priorityRules = append(pm.priorityRules[:i], pm.priorityRules[i+1:]...)
			return true
		}
	}

	return false
}

// GetPriorityRules returns all priority rules
func (pm *PriorityManager) GetPriorityRules() []PriorityRule {
	pm.mu.RLock()
	defer pm.mu.RUnlock()

	rules := make([]PriorityRule, len(pm.priorityRules))
	copy(rules, pm.priorityRules)
	return rules
}

// ClearHistory clears the access history
func (pm *PriorityManager) ClearHistory() {
	pm.mu.Lock()
	defer pm.mu.Unlock()

	pm.accessHistory = make(map[string]*AccessInfo)
	pm.filePriorities = make(map[string]float64)
}

// GetStatistics returns priority manager statistics
func (pm *PriorityManager) GetStatistics() PriorityManagerStats {
	pm.mu.RLock()
	defer pm.mu.RUnlock()

	return PriorityManagerStats{
		TotalFiles:      len(pm.accessHistory),
		TotalRules:      len(pm.priorityRules),
		AveragePriority: pm.calculateAveragePriority(),
		LastUpdated:     time.Now(),
	}
}

// calculateAveragePriority calculates the average priority of all files
func (pm *PriorityManager) calculateAveragePriority() float64 {
	if len(pm.filePriorities) == 0 {
		return 0.0
	}

	total := 0.0
	for _, priority := range pm.filePriorities {
		total += priority
	}

	return total / float64(len(pm.filePriorities))
}

// getDefaultPriorityRules returns the default priority rules
func getDefaultPriorityRules() []PriorityRule {
	return []PriorityRule{
		{
			Name:        "critical_system_files",
			Priority:    1.0,
			Description: "Critical system files",
			Condition: func(path string, info *AccessInfo) bool {
				criticalPaths := []string{"/bin", "/sbin", "/usr/bin", "/usr/sbin", "/etc", "/lib", "/usr/lib"}
				for _, criticalPath := range criticalPaths {
					if strings.HasPrefix(path, criticalPath) {
						return true
					}
				}
				return false
			},
		},
		{
			Name:        "executables",
			Priority:    0.9,
			Description: "Executable files",
			Condition: func(path string, info *AccessInfo) bool {
				pathExt := strings.ToLower(filepath.Ext(path))
				executableExts := []string{"", ".exe", ".bin", ".sh", ".py", ".pl", ".rb"}
				for _, ext := range executableExts {
					if ext == "" {
						// Check if it's a directory with execute permissions
						return true
					}
					if ext == pathExt {
						return true
					}
				}
				return false
			},
		},
		{
			Name:        "libraries",
			Priority:    0.8,
			Description: "Library files",
			Condition: func(path string, info *AccessInfo) bool {
				ext := strings.ToLower(filepath.Ext(path))
				libExts := []string{".so", ".dll", ".dylib", ".a", ".lib"}
				for _, libExt := range libExts {
					if ext == libExt {
						return true
					}
				}
				return false
			},
		},
		{
			Name:        "configuration_files",
			Priority:    0.7,
			Description: "Configuration files",
			Condition: func(path string, info *AccessInfo) bool {
				ext := strings.ToLower(filepath.Ext(path))
				configExts := []string{".conf", ".config", ".cfg", ".ini", ".json", ".yaml", ".yml", ".xml", ".properties"}
				for _, configExt := range configExts {
					if ext == configExt {
						return true
					}
				}
				return false
			},
		},
		{
			Name:        "frequently_accessed",
			Priority:    0.6,
			Description: "Frequently accessed files",
			Condition: func(path string, info *AccessInfo) bool {
				return info.AccessCount > 10
			},
		},
		{
			Name:        "recently_accessed",
			Priority:    0.5,
			Description: "Recently accessed files",
			Condition: func(path string, info *AccessInfo) bool {
				return time.Since(info.LastAccess) < 5*time.Minute
			},
		},
	}
}

// NewPatternAnalyzer creates a new pattern analyzer
func NewPatternAnalyzer() *PatternAnalyzer {
	return &PatternAnalyzer{
		patterns:        make(map[string]*AccessPattern),
		learningEnabled: true,
	}
}

// Analyze analyzes access patterns and returns priority adjustment
func (pa *PatternAnalyzer) Analyze(path string, _ FileEvent) float64 {
	if !pa.learningEnabled {
		return 0.0
	}

	pa.mu.Lock()
	defer pa.mu.Unlock()

	// Extract pattern from path
	pattern := pa.extractPattern(path)

	// Update pattern statistics
	if existing, exists := pa.patterns[pattern]; exists {
		existing.Frequency++
		existing.LastObserved = time.Now()
		// Increase confidence with more observations
		if existing.Confidence < 0.9 {
			existing.Confidence = min(existing.Confidence+0.1, 0.9)
		}
	} else {
		pa.patterns[pattern] = &AccessPattern{
			Pattern:      pattern,
			Frequency:    1,
			Confidence:   0.5,
			LastObserved: time.Now(),
		}
	}

	// Return priority adjustment based on pattern
	if patternInfo, exists := pa.patterns[pattern]; exists {
		if patternInfo.Confidence > 0.7 && patternInfo.Frequency > 5 {
			return 0.2 // Bonus for well-established patterns
		}
	}

	return 0.0
}

// extractPattern extracts a pattern from a file path
func (pa *PatternAnalyzer) extractPattern(path string) string {
	// Simple pattern extraction - can be enhanced with ML
	dir := filepath.Dir(path)
	base := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))

	// Look for numeric patterns
	if strings.Contains(base, "_") {
		parts := strings.Split(base, "_")
		for i, part := range parts {
			if isNumeric(part) {
				// Replace numeric parts with placeholder
				parts[i] = "{num}"
			}
		}
		return filepath.Join(dir, strings.Join(parts, "_")+filepath.Ext(path))
	}

	return filepath.Join(dir, "{file}"+filepath.Ext(path))
}

// PriorityManagerStats contains priority manager statistics
type PriorityManagerStats struct {
	TotalFiles      int
	TotalRules      int
	AveragePriority float64
	LastUpdated     time.Time
}

// Helper function
func min(a, b float64) float64 {
	if a < b {
		return a
	}
	return b
}